package com.sws.common.utils.access_tokenutil.util;


import com.alibaba.fastjson2.JSON;
import com.sws.common.utils.access_tokenutil.constant.TokenConstant;
import com.sws.common.utils.access_tokenutil.model.DeviceToken;
import com.sws.common.utils.access_tokenutil.model.LoginParam;
import com.sws.common.utils.access_tokenutil.model.LoginRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 访问令牌工具类
 * 用于获取、刷新设备访问令牌
 */
@Slf4j
public class AccessTokenUtil {

    /**
     * 默认令牌有效期（秒）
     */
    private static final long DEFAULT_TOKEN_EXPIRE_SECONDS = 3600;

    /**
     * 设备IP地址集合（记录所有已请求过token的设备IP）
     */
    private static final Set<String> deviceIpSet = new HashSet<>();
    
    /**
     * 设备SN与IP的映射关系缓存
     */
    private static final Map<String, String> deviceSnToIpMap = new HashMap<>();

    /**
     * Redis工具类
     */
    private static RedisUtil redisUtil;

    /**
     * 默认用户名
     */
    private static String defaultUsername;

    /**
     * 默认密码
     */
    private static String defaultPassword;

    /**
     * 默认设备IP地址
     */
    private static String defaultDeviceIp;

    /**
     * 初始化工具类配置
     *
     * @param redisUtilInstance Redis工具类实例
     * @param username          默认用户名
     * @param password          默认密码
     * @param deviceIp          默认设备IP地址
     */
    public static void init(RedisUtil redisUtilInstance, String username, String password, String deviceIp) {
        redisUtil = redisUtilInstance;
        defaultUsername = username;
        defaultPassword = password;
        defaultDeviceIp = deviceIp;
    }

    /**
     * 获取设备访问令牌
     *
     * @param deviceIp 设备IP地址
     * @return 设备令牌信息
     */
    public static DeviceToken getAccessToken(String deviceIp) {
        if (!StringUtils.hasLength(deviceIp)) {
            log.error("设备IP地址为空");
            return null;
        }

        // 构建Redis键
        String redisKey = buildRedisKey(deviceIp);

        // 尝试从Redis获取Token
        String tokenJson = (String) redisUtil.get(redisKey);
        if (StringUtils.hasLength(tokenJson)) {
            DeviceToken deviceToken = JSON.parseObject(tokenJson, DeviceToken.class);

            // 检查Token是否即将过期（小于10分钟），如果是则刷新
            if (isTokenAboutToExpire(deviceToken)) {
                log.info("设备[{}]的Token即将过期，准备刷新", deviceIp);
                refreshAccessToken(deviceIp);
                // 重新获取刷新后的Token
                tokenJson = (String) redisUtil.get(redisKey);
                deviceToken = JSON.parseObject(tokenJson, DeviceToken.class);
            }

            return deviceToken;
        }

        // Redis中不存在，请求新Token
        log.info("Redis中不存在设备[{}]的Token，准备请求新Token", deviceIp);
        boolean refreshResult = refreshAccessToken(deviceIp);
        if (refreshResult) {
            // 添加到设备IP集合
            deviceIpSet.add(deviceIp);
            // 获取新添加的Token
            tokenJson = (String) redisUtil.get(redisKey);
            return JSON.parseObject(tokenJson, DeviceToken.class);
        }

        return null;
    }
    
    /**
     * 根据设备SN获取访问令牌
     * 
     * @param deviceSn 设备序列号
     * @param deviceIpResolver 设备IP解析器接口，用于根据SN获取设备IP
     * @return 访问令牌信息
     */
    public static DeviceToken getAccessTokenBySn(String deviceSn, DeviceIpResolver deviceIpResolver) {
        if (!StringUtils.hasLength(deviceSn)) {
            log.error("设备序列号为空");
            return null;
        }
        
        if (deviceIpResolver == null) {
            log.error("设备IP解析器为空，无法获取设备[{}]的IP地址", deviceSn);
            return null;
        }
        
        log.info("开始根据设备SN[{}]获取访问令牌信息", deviceSn);
        
        // 从缓存中获取IP，如果不存在则通过解析器获取
        String deviceIp = deviceSnToIpMap.get(deviceSn);
        if (!StringUtils.hasLength(deviceIp)) {
            log.info("缓存中不存在设备SN[{}]对应的IP地址，尝试通过解析器获取", deviceSn);
            deviceIp = deviceIpResolver.resolveIpBySn(deviceSn);
            if (StringUtils.hasLength(deviceIp)) {
                // 更新缓存
                log.info("成功获取设备SN[{}]的IP地址[{}]并更新缓存", deviceSn, deviceIp);
                deviceSnToIpMap.put(deviceSn, deviceIp);
            } else {
                log.error("无法通过解析器获取设备SN[{}]的IP地址", deviceSn);
                return null;
            }
        } else {
            log.info("从缓存中获取到设备SN[{}]的IP地址[{}]", deviceSn, deviceIp);
        }
        
        // 调用现有方法获取令牌
        log.info("使用IP地址[{}]获取设备SN[{}]的访问令牌", deviceIp, deviceSn);
        DeviceToken deviceToken = getAccessToken(deviceIp);
        
        if (deviceToken != null) {
            log.info("成功获取设备SN[{}], IP[{}]的访问令牌", deviceSn, deviceIp);
        } else {
            log.error("获取设备SN[{}], IP[{}]的访问令牌失败", deviceSn, deviceIp);
        }
        
        return deviceToken;
    }
    
    /**
     * 设备IP解析器接口
     */
    public interface DeviceIpResolver {
        /**
         * 根据设备序列号解析IP地址
         * 
         * @param sn 设备序列号
         * @return 设备IP地址
         */
        String resolveIpBySn(String sn);
    }
    
    /**
     * 更新设备SN与IP的映射关系
     * 
     * @param deviceSn 设备序列号
     * @param deviceIp 设备IP地址
     */
    public static void updateDeviceSnIpMapping(String deviceSn, String deviceIp) {
        if (StringUtils.hasLength(deviceSn) && StringUtils.hasLength(deviceIp)) {
            deviceSnToIpMap.put(deviceSn, deviceIp);
        }
    }
    
    /**
     * 清除设备SN的IP映射缓存
     * 
     * @param deviceSn 设备序列号
     */
    public static void clearDeviceSnIpMapping(String deviceSn) {
        if (StringUtils.hasLength(deviceSn)) {
            deviceSnToIpMap.remove(deviceSn);
        }
    }

/**
 * 刷新设备访问令牌
 *
 * @param deviceIp 设备IP地址
 * @return 刷新结果
 */
public static boolean refreshAccessToken(String deviceIp) {
    if (!StringUtils.hasLength(deviceIp)) {
        log.error("设备IP地址为空");
        return false;
    }
    
    try {
        // 构建请求URL
        String url = "http://" + deviceIp + TokenConstant.API_PREFIX;
        
        // 构建请求参数
        LoginRequest loginRequest = buildLoginRequest();
        String paramJson = JSON.toJSONString(loginRequest);
        
        // 发送请求
        log.info("正在请求设备[{}]的访问令牌，参数: {}", deviceIp, paramJson);
        String responseJson = HttpClientUtil.doGet(url, paramJson);
        
        if (!StringUtils.hasLength(responseJson)) {
            log.error("请求设备[{}]的访问令牌失败，响应为空", deviceIp);
            return false;
        }
        
        // 手动解析JSON
        com.alibaba.fastjson2.JSONObject jsonObject = JSON.parseObject(responseJson);
        int code = jsonObject.getIntValue("code");
        
        if (code != TokenConstant.SUCCESS_CODE) {
            log.error("请求设备[{}]的访问令牌失败，响应: {}", deviceIp, responseJson);
            return false;
        }
        
        com.alibaba.fastjson2.JSONObject result = jsonObject.getJSONObject("result");
        String accessToken = result.getString("access_token");
        
        if (!StringUtils.hasLength(accessToken)) {
            log.error("请求设备[{}]的访问令牌失败，Token为空", deviceIp);
            return false;
        }
        
        // 构建设备Token信息
        DeviceToken deviceToken = new DeviceToken();
        deviceToken.setDeviceIp(deviceIp);
        deviceToken.setAccessToken(accessToken);
        deviceToken.setCreateTime(LocalDateTime.now());
        deviceToken.setExpireTime(LocalDateTime.now().plus(DEFAULT_TOKEN_EXPIRE_SECONDS, ChronoUnit.SECONDS));
        
        // 存储到Redis
        String redisKey = buildRedisKey(deviceIp);
        redisUtil.set(redisKey, JSON.toJSONString(deviceToken), DEFAULT_TOKEN_EXPIRE_SECONDS);
        
        log.info("成功获取并存储设备[{}]的访问令牌", deviceIp);
        return true;
    } catch (Exception e) {
        log.error("请求设备[{}]的访问令牌异常", deviceIp, e);
        return false;
    }
}

        /**
         * 刷新所有设备的访问令牌
         */
        public static void refreshAllAccessTokens () {
            log.info("开始刷新所有设备的访问令牌，设备数量: {}", deviceIpSet.size());

            for (String deviceIp : deviceIpSet) {
                try {
                    boolean result = refreshAccessToken(deviceIp);
                    if (result) {
                        log.info("设备[{}]的访问令牌刷新成功", deviceIp);
                    } else {
                        log.warn("设备[{}]的访问令牌刷新失败", deviceIp);
                    }
                } catch (Exception e) {
                    log.error("刷新设备[{}]的访问令牌异常", deviceIp, e);
                }
            }

            log.info("所有设备的访问令牌刷新完成");
        }

        /**
         * 构建登录请求
         *
         * @return 登录请求对象
         */
        private static LoginRequest buildLoginRequest () {
            LoginRequest loginRequest = new LoginRequest();
            loginRequest.setUri(TokenConstant.LOGIN_URI);

            LoginParam loginParam = new LoginParam();
            loginParam.setUsername(defaultUsername);

            // 加密密码：MD5(用户名+/system/account/login_in+用户密码)
            String encryptedPassword = MD5Util.encryptPassword(
                    defaultUsername, TokenConstant.LOGIN_URI, defaultPassword);
            loginParam.setPassword(encryptedPassword);

            loginRequest.setParam(loginParam);
            return loginRequest;
        }

        /**
         * 构建Redis键
         *
         * @param deviceIp 设备IP地址
         * @return Redis键
         */
        private static String buildRedisKey (String deviceIp){
            return TokenConstant.REDIS_TOKEN_KEY_PREFIX + deviceIp;
        }

        /**
         * 判断Token是否即将过期（小于10分钟）
         *
         * @param deviceToken 设备Token信息
         * @return 是否即将过期
         */
        private static boolean isTokenAboutToExpire (DeviceToken deviceToken){
            if (deviceToken == null || deviceToken.getExpireTime() == null) {
                return true;
            }

            LocalDateTime now = LocalDateTime.now();
            LocalDateTime expireTime = deviceToken.getExpireTime();

            // 计算剩余秒数
            long remainingSeconds = ChronoUnit.SECONDS.between(now, expireTime);

            // 如果剩余时间小于10分钟（600秒），则认为即将过期
            return remainingSeconds < 600;
        }

        /**
         * 直接获取设备访问令牌字符串
         * 简化开发人员使用，无需从DeviceToken对象中提取token
         *
         * @param deviceIp 设备IP地址
         * @return 访问令牌字符串，获取失败则返回null
         */
        public static String getToken (String deviceIp){
            DeviceToken deviceToken = getAccessToken(deviceIp);
            if (deviceToken != null) {
                return deviceToken.getAccessToken();
            }
            return null;
        }
        
        /**
         * 根据设备SN获取访问令牌字符串
         *
         * @param deviceSn 设备序列号
         * @param deviceIpResolver 设备IP解析器
         * @return 访问令牌字符串，获取失败则返回null
         */
        public static String getTokenBySn(String deviceSn, DeviceIpResolver deviceIpResolver) {
            log.info("开始根据设备SN[{}]获取访问令牌", deviceSn);
            
            if (deviceIpResolver == null) {
                log.error("设备IP解析器为空，无法获取设备[{}]的IP地址", deviceSn);
                return null;
            }
            
            String deviceIp = deviceIpResolver.resolveIpBySn(deviceSn);
            if (!StringUtils.hasLength(deviceIp)) {
                log.error("无法解析设备SN[{}]对应的IP地址", deviceSn);
                return null;
            }
            
            log.info("设备SN[{}]对应的IP地址为[{}]，开始获取访问令牌", deviceSn, deviceIp);
            String token = getToken(deviceIp);
            
            if (token == null) {
                log.error("获取设备SN[{}], IP[{}]的访问令牌失败", deviceSn, deviceIp);
            } else {
                log.info("成功获取设备SN[{}], IP[{}]的访问令牌", deviceSn, deviceIp);
            }
            
            return token;
        }

        public static String getdefaultDeviceIp () {
            return defaultDeviceIp;
        }
    }